/***************************************************************************
 *   Roeskva Test (20.08.2009)                                             *
 *   Copyright (C) 2009 by cmaster.matso (Maciej Kadłubowski)              *
 *   cmaster.matso@gmail.com                                               *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include <MGPI/mgpi.h>

/* Constants */
#define SCREEN_WIDTH 1280
#define SCREEN_HEIGHT 1024

/* STATIC Global Variables */
static Roeskva_t engine;
static MGPI_Handle hsbx, hhmp, hspr, hobj, hcamera;
static MGPI_Container *container;
static float speedf = 0.5f;

/* STATIC Routines Declarations */
static MGPI_BOOL key_proc(MGPI_Component *, SDL_Event *);
static MGPI_BOOL mouse_motion_proc(MGPI_Component *, SDL_Event *);
static MGPI_BOOL mouse_button_proc(MGPI_Component *, SDL_Event *);
static MGPI_BOOL _empty(MGPI_Component *, SDL_Event *);
static void skybox_trans(MGPI_RendNode *);
static void heightmap_trans(MGPI_RendNode *);
static void sprite_trans(MGPI_RendNode *);
static void obj_trans(MGPI_RendNode *);
static void user_engine_setup(void);
static void lighting_on(MGPI_RendNode *);
static void lighting_off(MGPI_RendNode *);

/* Strings - put your paths... */
#define DATA_STRING         "data\\"
#define TEXTURE_STRING      "data\\textures\\"
#define MODEL_STRING        "data\\models\\"
#define SKYBOX              DATA_STRING "skybox.sbx"
#define TERRAIN             TEXTURE_STRING "terrain.bmp"
#define PAPRIKA             TEXTURE_STRING "paprika.png"
#define MONKEY              MODEL_STRING "monkey.obj"
#define ROCKBUMP            TEXTURE_STRING "rockbump.jpg"
#define LOGO                TEXTURE_STRING "logo.jpg"
#define SKYBOXT             TEXTURE_STRING "skybox0.jpg"

/**
 * Initializes the Roeskva for work.
 */
static void init_engine(int *argc, char *argv[]) {
    float4 position = {128., 8., -128., 1.};
    /*float4 position = {SCREEN_WIDTH - 128.0f, SCREEN_HEIGHT - 128.0f, 0.0f, 1.0f};*/

    engImplement(&engine);
    {
        hcamera = engine.createCamera(MGPI_PERSPECTIVE);/* MGPI_ORTHOGRAPHIC*/

        engine.setProperty(MGPI_CAMERA_POSITION, hcamera, position);
        engine.setProperty(MGPI_CAMERA_NEAR, hcamera, 0.1f);
        engine.setProperty(MGPI_CAMERA_FAR, hcamera, 100000.0f);
        engine.setProperty(MGPI_CAMERA_FOVY, hcamera, 60.0f);
        engine.setProperty(MGPI_CAMERA_VIEWPORT, hcamera, SCREEN_WIDTH, SCREEN_HEIGHT);

        engine.setup(MGPI_SETUP_USER, user_engine_setup);
    }
    if (!engStart(&engine)) exit(EXIT_FAILURE);

    SDL_WM_SetCaption("Roeskva Test", NULL);
    SDL_ShowCursor(SDL_DISABLE);

    return;
}

/**
 * Sets up OpenGL parameters used in the engine.
 */
static void user_engine_setup(void) {
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

    glShadeModel(GL_SMOOTH);

    glClearDepth(1.0f);
    glDepthFunc(GL_LEQUAL);

    glEnable(GL_MULTISAMPLE_ARB);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);

    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
    return;
}

/**
 * Initializes the scene.
 */
static void init_scene(void) {
    int tex0 = 0, tex1 = 0, tex2 = 0;

    gsubSetTexCoordsWrapping(500.0f, 500.f);

    if ((hsbx = engine.loadObject(NULL, MGPI_SKYBOX)) < 0) exit(EXIT_FAILURE);
    if ((hhmp = engine.loadObject(TERRAIN, MGPI_HEIGHTMAP)) < 0) exit(EXIT_FAILURE);
    if ((hspr = engine.loadObject(PAPRIKA, MGPI_SPRITE)) < 0) exit(EXIT_FAILURE);
    if ((hobj = engine.loadObject(MONKEY, MGPI_MODEL_OBJ)) < 0) exit(EXIT_FAILURE);

    if (!(tex0 = g3dTexture2DType(ROCKBUMP, GL_RGB))) exit(EXIT_FAILURE);
    if (!(tex1 = g3dTexture2DType(LOGO, GL_RGB))) exit(EXIT_FAILURE);
    if (!(tex2 = g3dTexture2DType(SKYBOXT, GL_RGB))) exit(EXIT_FAILURE);

    engine.selectObject(hhmp);
    engine.setProperty(MGPI_TEXTURE, tex0, GL_TEXTURE_2D);
    
    engine.selectObject(hobj);
    engine.setProperty(MGPI_TEXTURE, tex1, GL_TEXTURE_2D);

    engine.selectObject(hsbx);
    engine.setProperty(MGPI_TEXTURE, tex2, GL_TEXTURE_2D);

    engine.setObjectTransformations(hsbx, skybox_trans);
    engine.setObjectTransformations(hhmp, heightmap_trans);
    engine.setObjectTransformations(hspr, sprite_trans);
    engine.setObjectTransformations(hobj, obj_trans);

    engine.setObjectGLCommands(hhmp, MGPI_GL_CMD_COMBINED, 2,//3,
            MGPI_GL_CMD_TEXTURE,
            MGPI_GL_CMD_USER, lighting_on, lighting_off);//,
            //MGPI_GL_CMD_FOG_EXP);
    
    engine.setObjectGLCommands(hobj, MGPI_GL_CMD_TEXTURE);
    engine.setObjectGLCommands(hspr, MGPI_GL_CMD_TEXTURE);
    engine.setObjectGLCommands(hsbx, MGPI_GL_CMD_TEXTURE);

    engine.setProperty(MGPI_ANIM_START, MGPI_NO_ANIMATION);
    engine.setProperty(MGPI_ANIM_STOP, MGPI_NO_ANIMATION);

    return;
}

/**
 * Initializes the scene container (for logic processing purposes).
 */
static void init_container(void) {
    container = (MGPI_Container *) calloc(1, sizeof (MGPI_Container));
    MGPI_CollectGarbage(container);

    container->component.onEvent[MGPI_MOUSE_BUTTON_DOWN] = mouse_button_proc;
    container->component.onEvent[MGPI_MOUSE_BUTTON_UP] = _empty;
    container->component.onEvent[MGPI_MOUSE_MOTION] = mouse_motion_proc;
    container->component.onEvent[MGPI_KEY_DOWN] = key_proc;
    container->component.onEvent[MGPI_KEY_UP] = _empty;
    return;
}
/*
static void glut_stuff(void) {

    static float angle = 0.0f;

    lighting_on(NULL);
    glPushMatrix();
    {
        glTranslatef(50.0f, 30.0f, -100.0f);
        glRotatef(angle, 0.0f, 1.0f, 0.0f);
        glFrontFace(GL_CW);
        glutSolidTeapot(15.0);
        glFrontFace(GL_CCW);

        glTranslatef(40.0f, 0.0f, 0.0f);
        glRotatef(2.0f * angle, 0.0f, 0.0f, 1.0f);
        glutSolidSphere(10.0f, 20, 50);
        
        glTranslatef(20.0f, 0.0f, 0.0f);
        glutSolidTorus(2.0, 5.0, 20, 20);
        angle += 0.2f;
    }
    glPopMatrix();
    lighting_off(NULL);

    return;
}
*/
/**
 * Renders the scene.
 */
static void render_scene(void) {/* WIN32: here is some thing wrong :/ */
    engine.updateCamera(hcamera);
    
    engine.waitFrame(60);

    engine.renderObject(hsbx);
    engine.renderObject(hhmp);
    engine.renderObject(hobj);
    engine.renderObject(hspr);

    g3dDrawCoordinates();
    engine.renderDiagnostic();

    engine.commitChanges();

    SDL_Delay(12);

    return;
}

/**
 * Routine called at program exit.
 */
static void at_exit(void) {
    engStop(&engine);
    MGPI_Exit();
    return;
}

/**
 * Transforms the skybox.
 */
static void skybox_trans(MGPI_RendNode *node) {
    float4 vect = MGPI_DEFAULT_VECTOR;
    g3dGetCamVect(&vect, hcamera, MGPI_CAMERA_POSITION);

    glTranslatef(vect[X], vect[Y], vect[Z]);
    glScalef(1000.0f, 1000.0f, 1000.0f);
    return;
}

/**
 * Transforms the height map.
 */
static void heightmap_trans(MGPI_RendNode *node) {
    glTranslatef(0.0f, -10.0f, 0.0f);
    glScalef(3.0f, 0.3f, 3.0f);
    g3dDrawCoordinates();
    return;
}

/**
 * Transforms the sprite.
 */
static void sprite_trans(MGPI_RendNode *node) {
    glTranslatef(0.0f, 120.0f, -200.0f);
    glScalef(15.0f, 15.0f, 1.0f);
    g3dDrawCoordinates();
    return;
}

/**
 * Transforms the OBJ model.
 */
static void obj_trans(MGPI_RendNode *node) {
    glTranslatef(200.0f, 20.0f, -200.0f);
    glScalef(3.0f, 3.0f, 3.0f);
    g3dDrawCoordinates();
    return;
}

/**
 * Defines lighting for the scene.
 */
static void lighting_on(MGPI_RendNode *node) {
    float4 light_pos0 = {100.0f, 300.0f, 0.0f, 1.0f};
    float4 light_pos1 = {100.0f, 300.0f, -200.0f, 1.0f};
    float4 light_pos2 = {100.0f, 300.0f, -500.0f, 1.0f};

    float4 light_red = {0.3f, 0.0f, 0.0f, 1.0f};
    float4 light_green = {0.0f, 0.3f, 0.0f, 1.0f};
    float4 light_blue = {0.0f, 0.0f, 0.3f, 1.0f};

    engine.setProperty(MGPI_LIGHTS);
    engine.setProperty(MGPI_GL_ON, GL_LIGHT0);
    engine.setProperty(MGPI_LIGHT_POSITION, GL_LIGHT0, light_pos0[0], light_pos0[1], light_pos0[2]);
    engine.setProperty(MGPI_LIGHT_DIFFUSE, GL_LIGHT0, light_red[0], light_red[1], light_red[2]);
    engine.setProperty(MGPI_LIGHT_ATTENUATION, GL_LIGHT0, 1.0f, 0.0f, 0.0f);

    engine.setProperty(MGPI_GL_ON, GL_LIGHT1);
    engine.setProperty(MGPI_LIGHT_POSITION, GL_LIGHT1, light_pos1[0], light_pos1[1], light_pos1[2]);
    engine.setProperty(MGPI_LIGHT_DIFFUSE, GL_LIGHT1, light_green[0], light_green[1], light_green[2]);
    engine.setProperty(MGPI_LIGHT_ATTENUATION, GL_LIGHT1, 1.0f, 0.0f, 0.0f);

    engine.setProperty(MGPI_GL_ON, GL_LIGHT2);
    engine.setProperty(MGPI_LIGHT_POSITION, GL_LIGHT2, light_pos2[0], light_pos2[1], light_pos2[2]);
    engine.setProperty(MGPI_LIGHT_DIFFUSE, GL_LIGHT2, light_blue[0], light_blue[1], light_blue[2]);
    engine.setProperty(MGPI_LIGHT_ATTENUATION, GL_LIGHT2, 1.0f, 0.0f, 0.0f);
    return;
}

/**
 * Defines lighting for the scene.
 */
static void lighting_off(MGPI_RendNode *node) {
    engine.setProperty(MGPI_GL_OFF, GL_LIGHT0);
    engine.setProperty(MGPI_GL_OFF, GL_LIGHT1);
    engine.setProperty(MGPI_GL_OFF, GL_LIGHT2);
    engine.setProperty(MGPI_LIGHTS);
    return;
}

/**
 * Processes key events.
 */
static MGPI_BOOL key_proc(MGPI_Component *component, SDL_Event *event) {
    float4 vect = MGPI_DEFAULT_VECTOR;
    int num;
    Uint8 *keys = SDL_GetKeyState(&num);

	if (keys[SDLK_ESCAPE]) return MGPI_FALSE;

    if (keys[SDLK_SPACE]) {
        g3dGetCamVect(&vect, hcamera, MGPI_CAMERA_UP);
        g3dTranslateCamf(hcamera, speedf * vect[0], speedf * vect[1], speedf * vect[2]);
    }

	if (keys[SDLK_ESCAPE]) return MGPI_FALSE;

    if (keys[SDLK_c]) {
        g3dGetCamVect(&vect, hcamera, MGPI_CAMERA_UP);
        g3dTranslateCamf(hcamera, -speedf * vect[0], -speedf * vect[1], -speedf * vect[2]);
    }

    if (keys[SDLK_q]) engine.setProperty(MGPI_TOGGLE_FULL_SCREEN);

    if (keys[SDLK_w]) {
        g3dGetCamVect(&vect, hcamera, MGPI_CAMERA_FORWARD);
        g3dTranslateCamf(hcamera, speedf * vect[0], speedf * vect[1], speedf * vect[2]);
    }

    if (keys[SDLK_s]) {
        g3dGetCamVect(&vect, hcamera, MGPI_CAMERA_FORWARD);
        g3dTranslateCamf(hcamera, -speedf * vect[0], -speedf * vect[1], -speedf * vect[2]);
    }

    if (keys[SDLK_a]) {
        g3dGetCamVect(&vect, hcamera, MGPI_CAMERA_RIGHT);
        g3dTranslateCamf(hcamera, -speedf * vect[0], -speedf * vect[1], -speedf * vect[2]);
    }

    if (keys[SDLK_d]) {
        g3dGetCamVect(&vect, hcamera, MGPI_CAMERA_RIGHT);
        g3dTranslateCamf(hcamera, speedf * vect[0], speedf * vect[1], speedf * vect[2]);
    }

    return MGPI_TRUE;
}

/**
 * Processes mouse motion events.
 */
static MGPI_BOOL mouse_motion_proc(MGPI_Component *component, SDL_Event *event) {
    static int last_x = SCREEN_WIDTH / 2;
    static int last_y = SCREEN_HEIGHT / 2;
    int current_x = event->motion.x;
    int current_y = event->motion.y;
    int dx = last_x - current_x;
    int dy = last_y - current_y;
    float4 right = MGPI_DEFAULT_VECTOR, up = {0.0f, 1.0f, 0.0f, 1.0f};

    g3dGetCamVect(&right, hcamera, MGPI_CAMERA_RIGHT);

    g3dRotateCamf(hcamera, (float) ((dy * MGPI_PI) / 180.0f), right);
    g3dRotateCamf(hcamera, (float) ((dx * MGPI_PI) / 180.0f), up);

    last_x = current_x;
    last_y = current_y;
    return MGPI_TRUE;
}

/**
 * Processes mouse buttons events.
 */
static MGPI_BOOL mouse_button_proc(MGPI_Component *component, SDL_Event *event) {
    switch (event->button.button) {
        case SDL_BUTTON_WHEELUP:
            g3dZoomIn(hcamera);
            break;

        case SDL_BUTTON_WHEELDOWN:
            g3dZoomOut(hcamera);
            break;

        default: break;
    }
    return MGPI_TRUE;
}

/**
 * Does nothing.
 */
static MGPI_BOOL _empty(MGPI_Component *component, SDL_Event *event) {
    return MGPI_TRUE;
}

/**
 * Program entry point.
 */
#ifdef WIN32
int APIENTRY WinMain(HINSTANCE hInstance, 
		HINSTANCE hPrevInstance,
		LPSTR lpCmdLine,
		int nCmdShow)
#else
int main(int argc, char *argv[]) 
#endif
{
    MGPI_Init();
    atexit(at_exit);

#ifdef WIN32
	glutInit(&nCmdShow, (char **) &lpCmdLine);
	init_engine(&nCmdShow, (char **) &lpCmdLine);
#else
    glutInit(&argc, argv);
	init_engine(&argc, argv);
#endif
    
    init_container();
    init_scene();

#ifndef WIN32
    MGPI_InitEvents(MGPI_THREAD_EVENTS, container);	/* WIN32 Bug: MGPI_STATE gets FALSE immediately?? */
    while (MGPI_STATE) render_scene();
#else
	while(MGPI_Listener(container)) render_scene();
#endif

    return 0;
}
